//
// $Id$

package com.samskievert.cactusboom;

import com.threerings.media.VirtualMediaPanel;
import com.threerings.toybox.util.ToyBoxContext;

import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.data.PlaceObject;
 
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Rectangle;

 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import com.threerings.presents.dobj.DSet;
 
 import com.threerings.presents.dobj.AttributeChangedEvent;
 import com.threerings.presents.dobj.AttributeChangeListener;
 import com.threerings.presents.dobj.EntryAddedEvent;
 import com.threerings.presents.dobj.EntryUpdatedEvent;
 import com.threerings.presents.dobj.EntryRemovedEvent;
 import com.threerings.presents.dobj.SetListener;

 import java.awt.event.MouseEvent;
 import java.awt.event.MouseMotionAdapter;
 import java.awt.event.MouseAdapter;
 
 import java.awt.Font;
 import com.samskivert.swing.Label;
 import com.threerings.media.animation.FloatingTextAnimation;
 import com.threerings.parlor.media.ScoreAnimation;
 import com.threerings.media.sprite.Sprite;
import javax.swing.plaf.SliderUI;
import java.awt.geom.AffineTransform;

import com.threerings.media.image.BufferedMirage;
import com.threerings.media.image.Mirage;

/**
 * Displays the main game interface (the board).
 */
public class CBBoardView extends VirtualMediaPanel
    implements PlaceView, SetListener, AttributeChangeListener
{
    
    public float SCALE = 1.0f;
    
    /**
     * Constructs a view which will initialize itself and prepare to display
     * the game board.
     */
    public CBBoardView (ToyBoxContext ctx, CBController ctrl)
    {
        super(ctx.getFrameManager());
        _ctx = ctx;
        _ctrl = ctrl; 
        
        //create the logic class
        _logic = new CBLogic(8);
        
        _cursor.setRenderOrder(-1);
        
       // listen for mouse motion and presses
       addMouseListener(new MouseAdapter() {
           public void mousePressed (MouseEvent e) {
               if(!_paused){
               CBObject.Cactus cactus = _cursor.getPiece();
               _logic.setState(_gameobj);
               if (_logic.isLegalMove(cactus,1) && spikeList.size() == 0) {
                   if(_cursor.moveType == 1){
                    _ctrl.cactusPlaced(cactus);
                   }else if (_cursor.moveType == 2){
                    _ctrl.addSpike(_cursor.overlap);
                   }else{
                    //if(_logic.getDirection(cactus, (int)(e.getX() / SCALE), (int)(e.getY() / SCALE), _cursor.SIZE) >= 0) _ctrl.moveCowboy(_cursor.quadrant);
                   }
                   _logic.setState(_gameobj);
                   if(gameStarted) _cursor.setPosition(cactus.x, cactus.y,_logic, _gameobj);                   
                   //setPlacingMode(-1);
               }else{

               }
           //finally, do the same thing as mouseMoved so we get a new cursor
               int tx = (int)(e.getX() / SCALE);
               int ty = (int)(e.getY() / SCALE);
               if(gameStarted) _cursor.setPosition(tx, ty,_logic, _gameobj);
           }
       }});

       
       // listen for mouse motion
       addMouseMotionListener(new MouseMotionAdapter() {
           public void mouseMoved (MouseEvent e) {
               if(!_paused){
               _logic.setState(_gameobj);
               int tx = (int)(e.getX() / SCALE);
               int ty = (int)(e.getY() / SCALE);
               if(gameStarted){
                   _cursor.setPosition(tx, ty,_logic, _gameobj);
                   
               }
           }}
       });

       
    }
    
   public void enableCursor()
   {
    if(_gameobj != null){
    //_ctrl.toggleView();
    gameStarted = true;
    _cursor.fixCactus(_gameobj.NUMSPIKES);
    }
   }
   
   //load the cowboy images from the media folder  
   //we only want to load the ones we're actually using...
   public void loadBVImages(){
       String[] filenames = {"media/spike.png","media/cactus.png"};
       for(int i = 0; i < 2; i++){
           BVImages[i] = new BufferedMirage(_ctx.loadImage(filenames[i]));
           System.out.println(filenames[i] + " loaded!");
       }
   }
   
   public void dirtyAll()
   {
        Iterator p = _gameobj.pieces.iterator();
        while(p.hasNext()){
          CBObject.Cactus cactus = (CBObject.Cactus)p.next();
          _sprites.get(cactus.getKey()).updateCactus(cactus);
        }
        
        //also go through and clear out all the dead ones
        Iterator s = _spritemgr.enumerateSprites();
        while(s.hasNext()){
          Object temp = s.next();
          if(s.getClass().getName().equalsIgnoreCase("CactusSprite"))
          {
            CactusSprite deadcheck = (CactusSprite)temp;
              if(deadcheck.getCactusKey() != 0){
                if(!_gameobj.pieces.containsKey(deadcheck.getCactusKey()))
                {
                    //System.out.println("Found a dead one!");
                    //removeSprite(_sprites.get(deadcheck));
                }
            }
          }
        }
   }
   

           
  
   @Override //from VirtualMediaPanel
   public void tick(long tickStamp)
   {
       super.tick(tickStamp);
       if(spikeList.size() > 0) _cursor._legal = false;
       else _cursor._legal = true;
       
       //we need to iterate through all of the sprites and sort out the SpikeSprites
       //if any of them are SpikeSprites and no longer needed (notSpent = false),
       //perform garbage collection
       
       boolean doneAndDone = false;
       for (int i = 0; i < spikeList.size(); i++){
            SpikeSprite spike = (SpikeSprite)spikeList.get(i);
            if(!spike.notSpent){
                removeSprite(spike);
                spikeList.remove(i);
                //System.out.println("One more dead spike! (" + spikeList.size() + ")");
                i--;
                if(spikeList.size() == 0) doneAndDone = true;
            }
          }
       
        if(doneAndDone){ //time to let the server know it can start paying attention again
           _cursor.invalidate();
            ArrayList<CBObject.Cactus> newSet = new ArrayList<CBObject.Cactus>();
            for(CBObject.Cactus cactus : _gameobj.pieces){
                if(!deadList.contains(cactus)) newSet.add(cactus);
            }
            
            CBObject.Cactus[] t = newSet.toArray(new CBObject.Cactus[newSet.size()]);
            DSet<CBObject.Cactus> newP = new DSet<CBObject.Cactus>(t);
            _gameobj.manager.invoke("gameOn",newP,_cursor.getPiece().owner);   
        }
   }
   
   
   /**
   * Floats the supplied text over the board.
   */
   public void displayFloatingText (String text)
   {
       Label label = ScoreAnimation.createLabel(
           text, Color.white, new Font("Helvetica", Font.BOLD, 48), this);
       int lx = (getWidth() - label.getSize().width)/2;
       int ly = (getHeight() - label.getSize().height)/2;
       addAnimation(new FloatingTextAnimation(label, lx, ly));
   }

    /**
    * Activates "placing" mode which allows the user to place a piece of the
    * specified color.
    */
   public void setPlacingMode (int color)
   {
       if(_gameobj != null){
       // update our logic with the current board state
       _logic.setState(_gameobj);
       }
       
       //getting duplicate sprites, so first clear it out and then
       //let it re-insert itself if it can
       //(more elegant woudld be to just fix the double insertion...)
       if(getSpriteManager().isManaged(_cursor)) removeSprite(_cursor);
       
       if (color != -1) {
           _cursor.setColor(color);
           addSprite(_cursor);
       } else if (isManaged(_cursor)) {
           removeSprite(_cursor);
       }
   }
 
   /** Used to determine legal moves. */
   protected CBLogic _logic;

   /** Displays a cursor when we're allowing the user to place a piece. */
   protected CursorSprite _cursor = new CursorSprite();

    // from interface PlaceView
    public void willEnterPlace (PlaceObject plobj)
    {
        _gameobj = (CBObject)plobj;
        _gameobj.addListener(this);
        
        loadBVImages();
        
        //create Sprites for each space on the game board
        for(CBObject.Cactus cactus : _gameobj.pieces){
            addCactusSprite(cactus);
        }
        
        //temporary hackery
        setPlacingMode(CBObject.BLUE);
    }

    // from interface PlaceView
    public void didLeavePlace (PlaceObject plobj)
    {
         _gameobj.removeListener(this);
         _gameobj = null;
    }
    
    public void clearSpikes(){
        for (int i = 0; i < spikeList.size(); i++){
            SpikeSprite spike = (SpikeSprite)spikeList.get(i);
            removeSprite(spike);
        }
        spikeList = new ArrayList<SpikeSprite>();        
    }
    
    
    //from interface AttributeChangeListener
    public void attributeChanged(AttributeChangedEvent event) 
    {
        if(event.getName().equals(CBObject.PIECES)){
            clearSprites();         
            setPlacingMode(_ctrl.turnCheck() ? _cursor.getPiece().owner : -1);
            
            //addSprite(_cursor);
            spikeList = new ArrayList<SpikeSprite>();
            deadList = new ArrayList<CBObject.Cactus>(); 
            _sprites =  new HashMap<Comparable,CactusSprite>();
            
            //create Sprites for each space on the game board
            int cc = 0;
            for(CBObject.Cactus cactus : _gameobj.pieces){
                addCactusSprite(cactus);
                cc++;
            }
        }
        
        if(event.getName().equals(CBObject.LOCALS_ONLY)){
            if(_gameobj.localsOnly) setPlacingMode(-1);
        }
        
    }

         // from interface SetListener
     public void entryAdded (EntryAddedEvent event)
     {
         if (event.getName().equals(CBObject.PIECES)) {
             // add a sprite for the newly created piece
             addCactusSprite((CBObject.Cactus)event.getEntry());
         }

     }
 
     // from interface SetListener
     public void entryUpdated (EntryUpdatedEvent event)
     {
         if (event.getName().equals(CBObject.PIECES)) {
             // update the sprite that is displaying the updated piece
             CBObject.Cactus cactus = (CBObject.Cactus)event.getEntry();
             if(_sprites.containsKey(cactus.getKey())) _sprites.get(cactus.getKey()).updateCactus(cactus);
         }
      }
     //i was noticing a little jitter when the client was waiting for updates from the 
     //client to update the sprites, so this is a function to update the sprite 
     //immediately.  When the update comes from the server, it checks to see if the two
     //are the same (which they should be) and fixes it otherwise
     public void quickUpdateCactus(CBObject.Cactus cactus)
     {
         if(!deadList.contains(cactus) && _sprites.containsKey(cactus.getKey())){  
            _sprites.get(cactus.getKey()).updateCactus(cactus);
            _gameobj.manager.invoke("doCollisionUpdate", cactus, false, spikeList.size());
         }
     }
     
     public void killCactusQuietly(CBObject.Cactus cactus)
     {
        if(!deadList.contains(cactus) && _sprites.containsKey(cactus.getKey())){
            
            _sprites.get(cactus.getKey()).updateCactus(cactus); // invalidate();
            //System.out.println("Cactus at " + cactus.x + " " + cactus.y + " now has " + cactus.getNumSpikes() + " spikes!");
            if(_gameobj.isFull(cactus)){
            //System.out.println("...so we're taking it off the market!");
            
            deadList.add(cactus);
            //System.out.println("LocalsOnly is " + _gameobj.localsOnly);
            removeSprite(_sprites.get(cactus.getKey()));
            //System.out.println("Collided with cactus " + cactus.getKey());
            int n = 0;
            for(int i = 0; i < cactus.spikes.length; i++){
                SpikeSprite ts = new SpikeSprite(cactus,i,_size,_gameobj,this,BVImages[0]);
                if(cactus.spikes[i] >= 0) {
                    spikeList.add(ts);
                    addSprite(ts);
                    n++;
                }
            }
            //System.out.println(n + " spikes created!");
            cactus.active = false;
        }}
     }
 
     // from interface SetListener
     public void entryRemoved (EntryRemovedEvent event)
     {
          if (event.getName().equals(CBObject.PIECES)) {
             CBObject.Cactus cactus = (CBObject.Cactus)event.getOldEntry();
             if(_sprites.containsKey(cactus.getKey())) {
                 removeSprite(_sprites.get(cactus.getKey()));
             
                for(int i = 0; i < cactus.spikes.length; i++){
                    SpikeSprite ts = new SpikeSprite(cactus,i,_size,_gameobj,this,BVImages[0]);
                    if(cactus.spikes[i] >= 0) {
                        spikeList.add(ts);
                        addSprite(ts);
                    }
                }
             }
         }
          
          if (event.getName().equals(CBObject.PICKUPS)) {
             CBObject.Pickup pickup = (CBObject.Pickup)event.getOldEntry();
             //removeSprite(_pickupSprites.get(pickup.getKey()));
          }
     }
 

     /**
      * Adds a sprite to the board for the supplied piece.
      */
     protected void addCactusSprite (CBObject.Cactus cactus)
     {
         CactusSprite sprite = new CactusSprite(cactus, BVImages[1]);
         _sprites.put(cactus.getKey(), sprite);
         addSprite(sprite);
     }
     
    
 
   @Override // from JComponent
   public Dimension getPreferredSize ()
   {
       //System.out.println("Preferred Size called (" + (int)(_size.width * SCALE * CactusSprite.SIZE + 1) +  "," + (int)(_size.height * SCALE * CactusSprite.SIZE + 1) + ")");
       
       //tempoooorary!
       //return new Dimension(200,200);
       return new Dimension((int)(_size.width * CactusSprite.SIZE + 1), (int)(_size.height * CactusSprite.SIZE + 1));
   }

   @Override // from MediaPanel
   protected void paintBehind (Graphics2D gfx, Rectangle dirtyRect)
   {
       //setSize((int)(_size.width * SCALE * CactusSprite.SIZE + 1), (int)(_size.height *SCALE * CactusSprite.SIZE + 1));
       AffineTransform currentRot = gfx.getTransform();
       //gfx.scale(SCALE, SCALE);
       
       super.paintBehind(gfx, dirtyRect);
 
       // fill in our background color
       gfx.setColor(new Color(225,219,196));
       gfx.fill(dirtyRect);
 
       // draw our grid
       gfx.setColor(Color.black);
       for (int yy = 0; yy <= _size.height; yy++) {
           int ypos = yy * CactusSprite.SIZE;
           gfx.drawLine(0, ypos, CactusSprite.SIZE * _size.width, ypos);
       } 
       for (int xx = 0; xx <= _size.width; xx++) {
           int xpos = xx * CactusSprite.SIZE;
           gfx.drawLine(xpos, 0, xpos, CactusSprite.SIZE * _size.height);
       }
       gfx.setTransform(currentRot);
       //paintDirtyRect(gfx, dirtyRect);
   }
    
  boolean gameStarted = false;
  int noSpikeCountDown = -1;
   
/** Contains a mapping from piece id to the sprite for that piece. */
   protected HashMap<Comparable, CactusSprite> _sprites =
         new HashMap<Comparable,CactusSprite>();
   
   /** Contains a mapping from piece id to the sprite for that piece. */
   protected HashMap<Comparable, CowboySprite> _cowboySprites = 
         new HashMap<Comparable,CowboySprite>();
   
  /** Contains a mapping from piece id to the sprite for that piece. */
   protected HashMap<Comparable, PickupSprite> _pickupSprites = 
         new HashMap<Comparable,PickupSprite>();   
   
   protected ArrayList<CBObject.Cactus> deadList = new ArrayList<CBObject.Cactus>();
   protected ArrayList<SpikeSprite> spikeList = new ArrayList<SpikeSprite>();

   protected BufferedMirage[] BVImages = new BufferedMirage[2];
   
   /**
     * The _size of the board.
     */
   protected Dimension _size = new Dimension(8, 8);

    /** Provides access to client services. */
    protected ToyBoxContext _ctx;

    /** A reference to our game object. */
    protected CBObject _gameobj;
    
   /** The game controller to which we dispatch user actions. */
   protected CBController _ctrl;
    
}
